use std::collections::HashMap;
use std::fs::File;
use futures_util::stream::StreamExt;
use liblzma::read::XzDecoder;
use tar::Archive;
use std::error::Error;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::Duration;
use indicatif::{ProgressBar, ProgressStyle};
use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, CONNECTION, REFERER, USER_AGENT};
use reqwest::Proxy;
use tokio::io::AsyncWriteExt;
use zip::ZipArchive;

#[derive(Debug)]
pub struct Installer{
    zig_version_map_url: String,
    zig_download_url: String,
    performance: String,
    zig_download_file_name: String,
    zig_download_file_path: String,
}

impl Installer {

    pub fn new() -> Self{
        Self{
            zig_version_map_url:String::from("https://ziglang.org/download/index.json"),
            zig_download_url: String::new(),
            performance: std::env::consts::ARCH.to_string() + "-" + std::env::consts::OS,
            zig_download_file_name: String::new(),
            zig_download_file_path: String::new(),
        }
    }
    pub async fn install_zig(& mut self,version: Option<String>) -> Result<(),Box<dyn Error>>{
        let resp = reqwest::get(&self.zig_version_map_url).await?;

        let resp = resp.error_for_status()?;

        let json_body: serde_json::Value = resp.json().await?;

        let zig_version_map: HashMap<String, serde_json::Value> = json_body
            .as_object()
            .unwrap()
            .iter()
            .map(|(key, value)| (key.clone(), value.clone()))
            .collect();
        //println!("{:#?}",self);
        match version {
            Some(v) => {
                self.zig_download_url=zig_version_map
                .get(&v).unwrap()
                .get(self.performance.clone()).unwrap()
                .get("tarball").unwrap()
                .to_string().trim_matches('"').to_string();
                self.zig_download_file_name=self.zig_download_url.split("/").last().unwrap().to_string().replace(".zip","")
                .replace(".tar.xz", "");
            },
            None => {
                self.zig_download_url=zig_version_map
                    .get("master").unwrap()
                    .get(self.performance.clone()).unwrap()
                    .get("tarball").unwrap().to_string().trim_matches('"').to_string();
                self.zig_download_file_name = self.zig_download_url.split("/").last().unwrap().to_string().replace(".zip","")
                .replace(".tar.xz", "");
            },
        }
        println!("{:#?}", self);
        self.download_from_url().await?;
        Ok(())
    }

    async fn download_from_url(&mut self) -> Result<(), Box<dyn Error>>{
        // 处理安装路径
        let path = self.check_path()?;
        // 下载

        self.downloader(&path).await?;

        // 解压
        if std::env::consts::OS.eq("windows") {
            self.unzip().await?;
            // 配置环境变量
            self.set_env()?;
        } else {
            self.untar().await?;
        };        
        Ok(())
    }


    fn set_env(&self) -> Result<(),Box<dyn Error>> {
        let home_path = std::env::home_dir().unwrap().join(".zigup").join(&self.zig_download_file_name).join(&self.zig_download_file_name);


        unsafe {
            std::env::set_var("ZIG_HOME",home_path.clone().to_str().unwrap().to_string());
        }
        println!("设置环境变量的 路径 ZIG_HOME:   {:#?}",home_path.clone());

        let path = std::env::var("PATH")?;
        let new_path = format!("{};{}",path,home_path.to_str().unwrap().to_string());

        Command::new("setx").arg("PATH").arg(&new_path).output()?;

        Ok(())
    }

    async fn untar(&self) -> Result<(),Box<dyn Error>>{
        let file = std::fs::File::open(self.zig_download_file_path.clone())?;
        let out_path = self.zig_download_file_path.clone().replace(".tar.xz", "");

        let xz_decoder = XzDecoder::new(file);

        let mut archive = Archive::new(xz_decoder);

        for entry in archive.entries()? {
            let mut entry = entry?;
            let path = entry.path()?;

            let target_path = Path::new(&out_path).join(path);

            if entry.header().entry_type().is_dir() {
                std::fs::create_dir_all(&target_path)?;
            }else {
                if let Some(parent) = target_path.parent(){
                    std::fs::create_dir_all(parent)?;
                }
                let mut output_file = File::create(&target_path)?;
                std::io::copy(&mut entry,&mut output_file)?;
            }
        };
        Ok(())
    }
    async fn unzip(&self) -> Result<(),Box<dyn Error>> {
        println!("下载的文件的 路径： {:#?}",self.zig_download_file_path);

        // 解压路径
        let unzip_path = self.zig_download_file_path.clone().replace(".zip","");
        let file = std::fs::File::open(&self.zig_download_file_path)?;
        let mut archive = ZipArchive::new(file)?;

        std::fs::create_dir_all(&unzip_path)?;

        for i in 0..archive.len() {
            let mut zfile = archive.by_index(i)?;
            let outpath = Path::new(&unzip_path).join(zfile.name());

            if zfile.name().ends_with('/') {
                std::fs::create_dir_all(&outpath)?;
            } else {
                if let Some(parent) = outpath.parent() {
                    std::fs::create_dir_all(parent)?;
                }
                let mut outfile = std::fs::File::create(&outpath)?;
                std::io::copy(&mut zfile,&mut outfile)?;
            }
        }


        Ok(())
    }

    async fn downloader(&mut self,path: &PathBuf) -> Result<(),Box<dyn Error>> {
        let file_name = self.zig_download_url.split("/").last().unwrap().to_string();
        let mut file_path = path.clone();
        file_path.push(file_name);

        self.zig_download_file_path=file_path.clone().to_str().unwrap().to_string();

        println!("{:#?}",&file_path);
        // https://zigmirror.hryx.net/zig/zig-x86-windows-0.15.0-dev.695+041eedc1c.zip
        // https://pkg.machengine.org/zig/
        let mut headers = HeaderMap::new();
        headers.insert(USER_AGENT,HeaderValue::from_static("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"));
        headers.insert(ACCEPT,HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"));
        headers.insert(ACCEPT_ENCODING, HeaderValue::from_static("gzip, deflate, br"));
        headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("en-US,en;q=0.9"));
        headers.insert(CONNECTION,HeaderValue::from_static("keep-alive"));
        headers.insert(REFERER, HeaderValue::from_static("https://ziglang.org/download/"));
        headers.insert("X-Client-Os",HeaderValue::from_static(std::env::consts::OS));
        headers.insert("X-Client-Arch",HeaderValue::from_static(std::env::consts::ARCH));


        let proxy = Proxy::all("http://127.0.0.1:2334")?.headers(headers.clone());

        let client = reqwest::Client::builder()
            .use_rustls_tls()
            .proxy(proxy)
            .default_headers(headers.clone())
            .build()?;

        let  resp = client.get(&self.zig_download_url).headers(headers.clone()).timeout(Duration::from_secs(100)).send().await?;
        if resp.status().is_success() {
            let total_size = resp.content_length().unwrap_or(0);

            let pb = ProgressBar::new(total_size);
            pb.set_style(ProgressStyle::default_bar().template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})")
                .unwrap().progress_chars("#>-"));

            let mut file = tokio::fs::File::create(&file_path).await?;

            let mut stream = resp.bytes_stream();
            let mut download = 0;
            while let Some(chunk) = stream.next().await {
                let chunk = chunk?;
                file.write_all(&chunk).await?;
                download += chunk.len();

                pb.set_position(download as u64);
                //println!(
                //    "Download {:.2}/{:.2} MB ({:.2}%)",
                //    download as f64/1048576.0,
                //    total_size as f64/1048576.0,
                //    (download as f64/ total_size as f64)*100.0
                //);
            }
        }
        Ok(())
    }

    fn check_path(&self) -> Result<PathBuf,Box<dyn Error>> {
        let  home_path = std::env::home_dir();

        let mut path = home_path.as_ref().unwrap().clone();

        println!("home path : {:#?}",&path);
        path.push(".zigup");
        if path.exists() {
            println!("路径已存在 : {:#?}",&path);
            Ok(path)
        }else {
            std::fs::create_dir_all(&path)?;
            println!("创建路径 成功： {:#?}",&path);
            Ok(path)
        }
    }

}

